//@author A0099566H
package logic;
import java.util.StringTokenizer;

import org.joda.time.DateTime;
import org.joda.time.DateTimeConstants;
import org.joda.time.LocalDate;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;

import storage.ScheduleEvent;
import storage.Storage;
import storage.UndoRedoStackEntry;

public class ScheduleAdder {
	 
	private static final int EVENT_TYPE_FLOATING = 0;
	private static final int EVENT_TYPE_DEADLINE = 1;
	private static final int EVENT_TYPE_TIMED = 2;
	private static final String FLOATING_EVENT_DATE = "99999999";
	private static final String ADD_COMMAND = "add";
	private static final String MESSAGE_INVALID_INPUT = "Invalid Input String! Please Re-try!";
		
	private int commandType;
	private Storage eventsList;
	
	public ScheduleAdder(Storage eventsList){
		this.commandType = -1;
		this.eventsList = eventsList;
	}
	
	public String addEvent(String commandsList, String UID){
		if (checkIfEmptyString(commandsList)) {
			this.commandType = checkCommandType(commandsList);

			ScheduleEvent eventToAdd = doCommand(this.commandType,commandsList);
			if (eventToAdd != null) {
				String successMessage = eventsList.addEvent(eventToAdd);
				eventsList.getUndoStack().push(new UndoRedoStackEntry(ADD_COMMAND, eventToAdd));
				eventsList.clearRedoStack();
				return successMessage;
			}
		}
		return (MESSAGE_INVALID_INPUT);
		
	}

	private boolean checkIfEmptyString(String commandsList) {
		return commandsList != null && commandsList.length() > 0;
	}
	
	public ScheduleEvent doCommand(int commandNumber, String commandsList){
		switch(commandNumber){
		case EVENT_TYPE_FLOATING:
			return createFloating(commandsList);
		case EVENT_TYPE_DEADLINE:
			return createDeadline(commandsList);
		case EVENT_TYPE_TIMED:
			return createTimed(commandsList);
		default:
			return null;
		}
	}
	
	public int checkCommandType(String commandsList){
		if(checkIfTimed(commandsList)){
			commandType = EVENT_TYPE_TIMED;
		}
		else if(checkIfDeadline(commandsList)){
			commandType = EVENT_TYPE_DEADLINE;
		}
		else{
			commandType = EVENT_TYPE_FLOATING;
		}
		return this.commandType;
	}

	private ScheduleEvent createFloating(String description) {
		return new ScheduleEvent(EVENT_TYPE_FLOATING,description,FLOATING_EVENT_DATE);
	}
	
	private ScheduleEvent createTimed(String commandsList) {
		String description = "null",startTime,endTime,date = "20121212";
		StringTokenizer token;
		description = getDescriptionOfEvent(commandsList);
		token = removeDescriptionOfEvent(commandsList);
		if(description == null)
			return null;
		
		String dateToBeChecked = token.nextToken();
		
		if (hasDay(dateToBeChecked)){
			date = getDateFromDay(date, dateToBeChecked);
		}
		else if (dateToBeChecked.length() <= 3){
			try{
				int number = Integer.parseInt(dateToBeChecked);
				if (invalidNumberOfDays(number)){
					return  null;
				}
				else{
					date = getDateFromDaysAndMonthsWithoutYears(date, token, number);
					}
					date = addYearsToDateFromDaysAndMonths(date);
					
			}catch(NumberFormatException e){
				return null;
			}
		}
		else if (!isValidDateFromDayMonthYearFormat(dateToBeChecked))
			return null;
		else
			date = dateToBeChecked;
		token = new StringTokenizer(commandsList.substring(commandsList.lastIndexOf("from")+5));
		String timeToBeChecked = token.nextToken();
		if (ifInvalidTime(timeToBeChecked))
			return null;
		else{
			startTime = timeToBeChecked;
		}
			token =new StringTokenizer(commandsList.substring(commandsList.lastIndexOf("to")+3));
			timeToBeChecked = token.nextToken();
			if (ifInvalidTime(timeToBeChecked))
				return null;
			else
			endTime = timeToBeChecked;
		if (ifStartTimeisAfterEndTime(startTime, endTime))
			return null;
		return new ScheduleEvent(EVENT_TYPE_TIMED,description,date,startTime,endTime);
	}

	private boolean ifStartTimeisAfterEndTime(String startTime, String endTime) {
		return Integer.parseInt(startTime)>Integer.parseInt(endTime);
	}

	private boolean ifInvalidTime(String timeToBeChecked) {
		return timeToBeChecked.length() < 4 || timeToBeChecked.length() > 4 || Integer.parseInt(timeToBeChecked) > 2359;
	}

	private boolean invalidNumberOfDays(int number) {
		return number < 0 || number > 31;
	}

	private boolean hasDay(String check) {
		return check.toLowerCase().equals("monday")||check.toLowerCase().equals("tuesday")||
				check.toLowerCase().equals("wednesday")||check.toLowerCase().equals("thursday")||
				check.toLowerCase().equals("friday")||check.toLowerCase().equals("saturday")||
				check.toLowerCase().equals("sunday")||check.toLowerCase().equals("today")||check.toLowerCase().equals("tomorrow");
	}

	private StringTokenizer removeDescriptionOfEvent(String commandsList) {
		StringTokenizer token;
		if (commandsList.toLowerCase().contains("monday")){
			token = new StringTokenizer(commandsList.substring(commandsList.lastIndexOf("on")-1));		
		}
		else if(commandsList.toLowerCase().contains("today"))
			token = new StringTokenizer(commandsList.substring(commandsList.lastIndexOf("today")));
		else if(commandsList.toLowerCase().contains("tomorrow")){
			token = new StringTokenizer(commandsList.substring(commandsList.lastIndexOf("tomorrow")));
		}
		else{
			token = new StringTokenizer(commandsList.substring(commandsList.lastIndexOf("on")+3));
		}
		return token;
	}

	private String getDescriptionOfEvent(String commandsList) {
		String description;
		if (commandsList.toLowerCase().contains("monday")){
			try{
				description = commandsList.substring(0,commandsList.lastIndexOf("monday"));
			description = description.substring(0,description.lastIndexOf("on")).trim();
			}catch(IndexOutOfBoundsException e){return null;}
		}
		else
			try{
			description = commandsList.substring(0,commandsList.lastIndexOf("on")).trim();
			}catch(IndexOutOfBoundsException e){return null;}
		return description;
	}
	
	private ScheduleEvent createDeadline(String commandsList) {
		String description = "null" ,date = "null",endTime;
		StringTokenizer token;
		description = getDescriptionOfEvent(commandsList);
		token = removeDescriptionOfEvent(commandsList);
		if(description == null)
			return null;
		String dateToBeChecked = token.nextToken();
		if (hasDay(dateToBeChecked)){
			date = getDateFromDay(date, dateToBeChecked);
		}
		else if (dateToBeChecked.length() <= 3){
			try{
				int number = Integer.parseInt(dateToBeChecked);
				if (invalidNumberOfDays(number)){
					return  null;
				}
				else{
					date = getDateFromDaysAndMonthsWithoutYears(date, token, number);
				}
				date = addYearsToDateFromDaysAndMonths(date);
			}catch(NumberFormatException e){}
		}
		else if (!isValidDateFromDayMonthYearFormat(dateToBeChecked))
			return null;
		else
			date = dateToBeChecked;
		if(noEndTimeCheck(commandsList))
			endTime = "2359";
		else{
			String timeToBeChecked = getEndTime(commandsList);
			if (ifInvalidTime(timeToBeChecked))
				return null;
			else
				endTime = timeToBeChecked;
		}
		return new ScheduleEvent(EVENT_TYPE_DEADLINE,description,date,endTime);
	}

	private String addYearsToDateFromDaysAndMonths(String date) {
		DateTime stuff = new DateTime();
		date = date.concat(String.valueOf(stuff.getYear()));
		DateTimeFormatter formatter = DateTimeFormat.forPattern("ddMMyyyy");
		DateTime dt = formatter.parseDateTime(date);
		
		if(stuff.isAfter(dt)){
			date = dt.plusYears(1).toString("ddMMyyyy");
		}
		return date;
	}

	private String getDateFromDaysAndMonthsWithoutYears(String date, StringTokenizer token,
			int number) {
		String month = token.nextToken().toLowerCase();
		if(month.contains("january")|| month.contains("jan")){
			date = String.valueOf(number).concat("01");
		}
		if(month.contains("february")|| month.contains("feb")){
				date = String.valueOf(number).concat("02");
		}
		if(month.contains("march")|| month.contains("mar")){
			date = String.valueOf(number).concat("03");
		}
		if(month.contains("april")|| month.contains("apr")){
			date = String.valueOf(number).concat("04");
		}
		if(month.contains("may")){
			date = String.valueOf(number).concat("05");
		}
		if(month.contains("june")|| month.contains("june")){
			date = String.valueOf(number).concat("06");
		}
		if(month.contains("july")|| month.contains("july")){
			date = String.valueOf(number).concat("07");
		}
		if(month.contains("august")|| month.contains("aug")){
			date = String.valueOf(number).concat("08");
		}
		if(month.contains("september")|| month.contains("sep")){
			date = String.valueOf(number).concat("09");
		}
		if(month.contains("october")|| month.contains("oct")){
			date = String.valueOf(number).concat("10");
		}
		if(month.contains("november")|| month.contains("nov")){
			date = String.valueOf(number).concat("11");
		}
		if(month.contains("december")|| month.contains("dec")){
			date = String.valueOf(number).concat("12");
		}
		return date;
	}

	private String getEndTime(String commandsList) {
		StringTokenizer token;
		token =new StringTokenizer(commandsList.substring(commandsList.lastIndexOf("by")+3));
		String timeToBeChecked = token.nextToken();
		return timeToBeChecked;
	}

	private boolean noEndTimeCheck(String commandsList) {
		return commandsList.contains("by") == false;
	}

	private String getDateFromDay(String date, String check) {
		LocalDate today = new LocalDate();
		if (check.toLowerCase().equals("today")){
			date = today.toString("ddMMyyyy");
		}
		else if (check.toLowerCase().equals("tomorrow")){
			date = today.plusDays(1).toString("ddMMyyyy");
		}
		else if(check.toLowerCase().equals("monday")){
			today = calcNextMonday(today);
			date = today.toString("ddMMyyyy");
		}
		else if(check.toLowerCase().equals("tuesday")){
			today = calcNextTuesday(today);
			date = today.toString("ddMMyyyy");
		}
		else if(check.toLowerCase().equals("wednesday")){
			today = calcNextWednesday(today);
			date = today.toString("ddMMyyyy");
		}
		else if(check.toLowerCase().equals("thursday")){
			today = calcNextThursday(today);
			date = today.toString("ddMMyyyy");
		}
		else if(check.toLowerCase().equals("friday")){
			today = calcNextFriday(today);
			date = today.toString("ddMMyyyy");
		}
		else if(check.toLowerCase().equals("saturday")){
			today = calcNextSaturday(today);
			date = today.toString("ddMMyyyy");
		}
		else if(check.toLowerCase().equals("sunday")){
			today = calcNextSunday(today);
			date = today.toString("ddMMyyyy");
		}
		return date;
	}
	
	private Boolean checkIfTimed(String commandsList){
		Boolean check = true;
		if(!commandsList.contains("from")){
			check = false;
		}
		else{
			StringTokenizer tokens;
			try{
			tokens = new StringTokenizer(commandsList.substring(commandsList.lastIndexOf("from")+5));
			String toBeChecked = tokens.nextToken();
			try{
				Integer.parseInt(toBeChecked);
			}catch(NumberFormatException e){
				check = false;
			}
			}catch(IndexOutOfBoundsException e){
				check = false;
			}

		}
		return check;
	}


	private Boolean checkIfDeadline(String commandsList){
		Boolean check = false;
		StringTokenizer token;
		if (hasTodayOrTomorrow(commandsList))
			check = true;
		else if(ifHasNameOfDay(commandsList))
			check = true;
		else {
			token = new StringTokenizer(commandsList);
			check = checkIfDateAfterKeywordOn(commandsList, check, token);

		}
			return check;
	}

	private Boolean checkIfDateAfterKeywordOn(String commandsList,
			Boolean check, StringTokenizer token) {
		String contain;
		if(commandsList.toLowerCase().contains("on")){
			while (token.hasMoreTokens()){
				contain = token.nextToken();
				if(contain.toLowerCase().equals("on")){
					contain = token.nextToken();
					try{
						Integer.parseInt(contain);
						check = true;
					}catch(IndexOutOfBoundsException e){}
			}
			
			}	
		}
		return check;
	}

	private boolean ifHasNameOfDay(String commandsList) {
		return commandsList.toLowerCase().contains("monday")||commandsList.toLowerCase().contains("tuesday")||
				commandsList.toLowerCase().contains("wednesday")||commandsList.toLowerCase().contains("thursday")||
				commandsList.toLowerCase().contains("friday")||commandsList.toLowerCase().contains("saturday")||
				commandsList.toLowerCase().contains("sunday");
	}

	private boolean hasTodayOrTomorrow(String commandsList) {
		return commandsList.toLowerCase().contains("today")||commandsList.toLowerCase().contains("tomorrow");
	}
	 public boolean isValidDateFromDayMonthYearFormat(String test) {
		    try {
		      DateTimeFormatter dtf = DateTimeFormat.forPattern("ddMMyyyy");
		      dtf.parseDateTime(test);
		      return true;
		    } catch (IllegalArgumentException iae) {
		      return false;
		    }
		  }
	private static LocalDate calcNextMonday(LocalDate d) {
	    if (d.getDayOfWeek() >= DateTimeConstants.MONDAY) {
	        d = d.plusWeeks(1);
	    }
	    return d.withDayOfWeek(DateTimeConstants.MONDAY);
	}
	
	private static LocalDate calcNextTuesday(LocalDate d) {
	    if (d.getDayOfWeek() >= DateTimeConstants.TUESDAY) {
	        d = d.plusWeeks(1);
	    }
	    return d.withDayOfWeek(DateTimeConstants.TUESDAY);
	}private static LocalDate calcNextWednesday(LocalDate d) {
	    if (d.getDayOfWeek() >= DateTimeConstants.WEDNESDAY) {
	        d = d.plusWeeks(1);
	    }
	    return d.withDayOfWeek(DateTimeConstants.WEDNESDAY);
	}private static LocalDate calcNextThursday(LocalDate d) {
	    if (d.getDayOfWeek() >= DateTimeConstants.THURSDAY) {
	        d = d.plusWeeks(1);
	    }
	    return d.withDayOfWeek(DateTimeConstants.THURSDAY);
	}
	private static LocalDate calcNextFriday(LocalDate d) {
	    if (d.getDayOfWeek() >= DateTimeConstants.FRIDAY) {
	        d = d.plusWeeks(1);
	    }
	    return d.withDayOfWeek(DateTimeConstants.FRIDAY);
	}
	private static LocalDate calcNextSaturday(LocalDate d) {
	    if (d.getDayOfWeek() >= DateTimeConstants.SATURDAY) {
	        d = d.plusWeeks(1);
	    }
	    return d.withDayOfWeek(DateTimeConstants.SATURDAY);
	}
	private static LocalDate calcNextSunday(LocalDate d) {
	    if (d.getDayOfWeek() >= DateTimeConstants.SUNDAY) {
	        d = d.plusWeeks(1);
	    }
	    return d.withDayOfWeek(DateTimeConstants.SUNDAY);
	}
}